package com.fkdx.common.sequence;

import com.fkdx.common.enums.sys.GroupException;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;

/**
 *
 */
@Log4j2
@Component
public class BusinessSequece {

    private final static String SELECT_SQL = "SELECT id,format,suffix_length FROM sequence_business_value WHERE business = ? and format = ? ";
    private final static String INSERT_SQL = "INSERT INTO sequence_business_value (business, format, suffix_length, id) VALUES (?, ?, ?, ?)";
    private final static String UPDATE_SQL = "UPDATE sequence_business_value SET id = ?  WHERE business = ? AND id = ? and format = ? ";

    private Map<BusinessSequenceEnum, Step> stepMap = new HashMap<>();

    private int blockSize = 1;
    private Integer startValue = 0;


    @Resource
    private DataSource dataSource;

    // in | out |
    public synchronized String get(BusinessSequenceEnum sequenceEnum,String prefix) {
        Step step = stepMap.get(sequenceEnum);
        if(step == null) {
            step = new Step(startValue,startValue + blockSize, sequenceEnum.getFormat());
            stepMap.put(sequenceEnum, step);
        } else {
            if (step.currentValue < step.endValue) {
                return formatData(sequenceEnum, step, prefix);
            }
        }
        for (int i = 0; i < blockSize; i++) {
            if (getNextBlock(sequenceEnum, step)) {
                return formatData(sequenceEnum, step, prefix);
            }
        }
        throw new GroupException("No more value.");
    }

    //格式化数据
    private String formatData(BusinessSequenceEnum sequenceEnum, Step step, String prefix){
        long sn = step.incrementAndGet();
        String no = prefix + sequenceEnum.getFormat() + String.format("%0" + sequenceEnum.getSuffixLength() + "d", sn);
        log.info("formatData======{}",no);
        return no;
    }

    private boolean getNextBlock(BusinessSequenceEnum sequenceName, Step step) {
        Integer value = getPersistenceValue(sequenceName);
        if (value == null) {
            try {
                value = newPersistenceValue(sequenceName);
            } catch (Exception e) {
                log.error("newPersistenceValue error!");
                value = getPersistenceValue(sequenceName);
            }
        }

        boolean b = saveValue(value, sequenceName) == 1;
        if (b) {
            step.setCurrentValue(value);
            step.setEndValue(value+blockSize);
        }
        return b;
    }

    private int saveValue(Integer value, BusinessSequenceEnum sequenceName) {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            connection = dataSource.getConnection();
            statement = connection.prepareStatement(UPDATE_SQL);
            statement.setInt(1, value + blockSize);
            statement.setString(2, sequenceName.getName());
            statement.setInt(3, value);
            statement.setString(4, sequenceName.getFormat());
            return statement.executeUpdate();
        } catch (Exception e) {
            log.error("newPersistenceValue error!", e);
            throw new GroupException("newPersistenceValue error!", e);
        } finally {
            closeStatement(statement);
            closeConnection(connection);
        }
    }

    private Integer getPersistenceValue(BusinessSequenceEnum businessSequenceEnum) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            connection = dataSource.getConnection();
            statement = connection.prepareStatement(SELECT_SQL);
            statement.setString(1, businessSequenceEnum.getName());
            statement.setString(2, businessSequenceEnum.getFormat());
            resultSet = statement.executeQuery();
            if (resultSet.next()) {
                return resultSet.getInt("id");
            }
        } catch (Exception e) {
            log.error("getPersistenceValue error!", e);
            throw new GroupException("getPersistenceValue error!", e);
        } finally {
            closeResultSet(resultSet);
            closeStatement(statement);
            closeConnection(connection);
        }
        return null;
    }

    private Integer newPersistenceValue(BusinessSequenceEnum sequenceName) {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            connection = dataSource.getConnection();
            statement = connection.prepareStatement(INSERT_SQL);
            //business, format, zore_num, id
            statement.setString(1, sequenceName.getName());
            statement.setString(2, sequenceName.getFormat());
            statement.setInt(3, sequenceName.getSuffixLength());
            statement.setInt(4, startValue);
            statement.executeUpdate();
        } catch (Exception e) {
            log.error("newPersistenceValue error!", e);
            throw new GroupException("newPersistenceValue error!", e);
        } finally {
            closeStatement(statement);
            closeConnection(connection);
        }
        return startValue;
    }

    private void closeResultSet(ResultSet resultSet) {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                log.error("close resultset error!", e);
            }
        }
    }
    private void closeStatement(Statement statement) {
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                log.error("close statement error!", e);
            }
        }
    }

    private void closeConnection(Connection connection) {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                log.error("close connection error!", e);
            }
        }
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public void setBlockSize(int blockSize) {
        this.blockSize = blockSize;
    }

    public void setStartValue(Integer startValue) {
        this.startValue = startValue;
    }

    static class Step {
        private Integer currentValue;
        private Integer endValue;
        private String format;

        Step(Integer currentValue, Integer endValue, String format) {
            this.currentValue = currentValue;
            this.endValue = endValue;
            this.format = format;
        }

        public void setCurrentValue(Integer currentValue) {
            this.currentValue = currentValue;
        }

        void setEndValue(Integer endValue) {
            this.endValue = endValue;
        }

        void setFormat(String format) {
            this.format = format;
        }

        Integer incrementAndGet() {
            return ++currentValue;
        }
    }

    /**
     * 不够位数的在前面补0，保留num的长度位数字
     * @param zoreNum
     * @return
     */
    private static String autoGenericCode(Integer value, int zoreNum) {
        String result = "";
        // 保留num的位数
        // 0 代表前面补充0
        // num 代表长度多少
        // d 代表参数为正数型
        result = String.format("%0" + zoreNum + "d", value);
        return result;
    }

    public static void main(String[] args) {
        System.out.println(autoGenericCode(4,5));
        System.out.println(autoGenericCode(3,5));
        System.out.println(autoGenericCode(2,5));
        System.out.println(String.format("%04d", 1));
        String a = "20200809001";//2
        System.out.println(a.substring(a.length() - 1 ,a.length()));
        String b = "202008090011";//2
        System.out.println(a.substring(a.length() - 2 ,a.length()));

    }

}
